package org.exoplatform.navigation.webui;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.exoplatform.commons.utils.HTMLEntityEncoder;
import org.exoplatform.portal.mop.Described.State;
import org.exoplatform.portal.mop.Visibility;
import org.exoplatform.portal.mop.navigation.NodeChangeListener;
import org.exoplatform.portal.mop.navigation.NodeState;
import org.exoplatform.portal.mop.page.PageKey;
import org.exoplatform.portal.mop.user.UserNavigation;
import org.exoplatform.portal.mop.user.UserNode;
import org.exoplatform.portal.webui.util.Util;

/**
 * A wrapper class of {@link UserNode} for manipulation in WebUI part
 *
 * @author <a href="mailto:trong.tran@exoplatform.com">Trong Tran</a>
 * @version $Revision$
 */
public class TreeNode implements NodeChangeListener<UserNode> {
    private Map<String, TreeNode> caches;

    private UserNavigation nav;

    private UserNode node;

    private TreeNode rootNode;

    private boolean deleteNode = false;

    private boolean cloneNode = false;

    private String id;

    private List<TreeNode> children;

    private Map<Locale, State> i18nizedLabels;

    public TreeNode(UserNavigation nav, UserNode node) {
        this(nav, node, null);
        this.rootNode = this;
        this.caches = new HashMap<String, TreeNode>();
        addToCached(this);
    }

    private TreeNode(UserNavigation nav, UserNode node, TreeNode rootNode) {
        this.rootNode = rootNode;
        this.nav = nav;
        this.node = node;
    }

    public List<TreeNode> getChildren() {
        if (children == null) {
            children = new LinkedList<TreeNode>();
            for (UserNode child : node.getChildren()) {
                String key = child.getId() == null ? String.valueOf(child.hashCode()) : child.getId();
                TreeNode node = findNode(key);
                if (node == null) {
                    throw new IllegalStateException("Can' find node " + child.getURI() + " in the cache");
                }
                children.add(node);
            }
        }
        return children;
    }

    public TreeNode getChild(String name) {
        UserNode child = node.getChild(name);
        if (child == null) {
            return null;
        }
        return findNode(child.getId() == null ? String.valueOf(child.hashCode()) : child.getId());
    }

    public boolean removeChild(TreeNode child) {
        children = null;
        if (child == null) {
            return false;
        }
        removeFromCached(child);
        return node.removeChild(child.getName());
    }

    public TreeNode getParent() {
        UserNode parent = node.getParent();
        if (parent == null)
            return null;

        return findNode(parent.getId() == null ? String.valueOf(parent.hashCode()) : parent.getId());
    }

    public TreeNode getChild(int childIndex) throws IndexOutOfBoundsException {
        UserNode child = node.getChild(childIndex);
        if (child == null) {
            return null;
        }
        return findNode(child.getId() == null ? String.valueOf(child.hashCode()) : child.getId());
    }

    public TreeNode addChild(String childName) {
        children = null;
        UserNode child = node.addChild(childName);
        return addToCached(new TreeNode(nav, child, this.rootNode));
    }

    public void addChild(TreeNode child) {
        TreeNode oldParent = child.getParent();
        if (oldParent != null) {
            oldParent.children = null;
        }
        children = null;
        this.node.addChild(child.getNode());
    }

    public void addChild(int index, TreeNode child) {
        TreeNode oldParent = child.getParent();
        if (oldParent != null) {
            oldParent.children = null;
        }
        children = null;
        node.addChild(index, child.getNode());
    }

    public TreeNode findNode(String nodeID) {
        return this.rootNode.caches.get(nodeID);
    }

    public UserNode getNode() {
        return node;
    }

    public UserNavigation getPageNavigation() {
        return nav;
    }

    public boolean isDeleteNode() {
        return deleteNode;
    }

    public void setDeleteNode(boolean deleteNode) {
        this.deleteNode = deleteNode;
    }

    public boolean isCloneNode() {
        return cloneNode;
    }

    public void setCloneNode(boolean b) {
        cloneNode = b;
    }

    public String getPageRef() {
        return node.getPageRef() != null ? node.getPageRef().format() : null;
    }

    public String getId() {
        if (this.id == null) {
            this.id = node.getId() == null ? String.valueOf(node.hashCode()) : node.getId();
        }
        return this.id;
    }

    public String getURI() {
        return node.getURI();
    }

    public String getIcon() {
        return node.getIcon();
    }

    public void setIcon(String icon) {
        node.setIcon(icon);
    }

    public String getEncodedResolvedLabel() {
        if (getLabel() == null) {
            if (i18nizedLabels != null) {
                Locale locale = Util.getPortalRequestContext().getLocale();
                for (Locale key : i18nizedLabels.keySet()) {
                    if (key.equals(locale)) {
                        String label = i18nizedLabels.get(key).getName();
                        if (label == null || label.trim().length() == 0) {
                            return node.getName();
                        }

                        return HTMLEntityEncoder.getInstance().encode(label);
                    }
                }
            }
        }
        String encodedLabel = node.getEncodedResolvedLabel();
        return encodedLabel == null ? "" : encodedLabel;
    }

    public String getName() {
        return node.getName();
    }

    public void setName(String name) {
        node.setName(name);
    }

    public String getLabel() {
        return node.getLabel();
    }

    public void setLabel(String label) {
        node.setLabel(label);
    }

    public Visibility getVisibility() {
        return node.getVisibility();
    }

    public void setVisibility(Visibility visibility) {
        node.setVisibility(visibility);
    }

    public long getStartPublicationTime() {
        return node.getStartPublicationTime();
    }

    public void setStartPublicationTime(long startPublicationTime) {
        node.setStartPublicationTime(startPublicationTime);
    }

    public long getEndPublicationTime() {
        return node.getEndPublicationTime();
    }

    public void setEndPublicationTime(long endPublicationTime) {
        node.setEndPublicationTime(endPublicationTime);
    }

    public boolean isRestrictOutsidePublicationWindow() {
        return node.isRestrictOutsidePublicationWindow();
    }

    public void setRestrictOutsidePublicationWindow(boolean restrictOutsidePublicationWindow) {
        node.setRestrictOutsidePublicationWindow(restrictOutsidePublicationWindow);
    }

    public void setPageRef(String pageRef) {
        node.setPageRef(pageRef != null ? PageKey.parse(pageRef) : null);
    }

    public String getResolvedLabel() {
        String resolvedLabel = node.getResolvedLabel();
        return resolvedLabel == null ? "" : resolvedLabel;
    }

    public boolean hasChildrenRelationship() {
        return node.hasChildrenRelationship();
    }

    public int getChildrenCount() {
        return node.getChildrenCount();
    }

    private TreeNode addToCached(TreeNode node) {
        if (node == null) {
            return null;
        }

        if (findNode(node.getId()) != null) {
            return node;
        }

        this.rootNode.caches.put(node.getId(), node);
        for (UserNode child : node.getNode().getChildren()) {
            addToCached(new TreeNode(nav, child, this.rootNode));
        }
        return node;
    }

    private TreeNode removeFromCached(TreeNode node) {
        if (node == null) {
            return null;
        }

        this.rootNode.caches.remove(node.getId());
        if (node.hasChildrenRelationship()) {
            for (TreeNode child : node.getChildren()) {
                removeFromCached(child);
            }
        }
        return node;
    }

    @Override
    public void onAdd(UserNode target, UserNode parent, UserNode previous) {
        addToCached(new TreeNode(this.nav, target, this.rootNode));
        findNode(parent.getId()).children = null;
    }

    @Override
    public void onCreate(UserNode target, UserNode parent, UserNode previous, String name) {
    }

    @Override
    public void onRemove(UserNode target, UserNode parent) {
        removeFromCached(findNode(target.getId()));
        findNode(parent.getId()).children = null;
    }

    @Override
    public void onDestroy(UserNode target, UserNode parent) {
    }

    @Override
    public void onRename(UserNode target, UserNode parent, String name) {
    }

    @Override
    public void onUpdate(UserNode target, NodeState state) {
    }

    @Override
    public void onMove(UserNode target, UserNode from, UserNode to, UserNode previous) {
        TreeNode fromTreeNode = findNode(from.getId());
        TreeNode toTreeNode = findNode(to.getId());
        fromTreeNode.children = null;
        toTreeNode.children = null;
    }

    public void setI18nizedLabels(Map<Locale, State> labels) {
        this.i18nizedLabels = labels;
    }

    public Map<Locale, State> getI18nizedLabels() {
        return i18nizedLabels;
    }
}
